import tkinter as tk
import tkinter.font as tf
from tkinter import messagebox, filedialog, simpledialog, colorchooser
from tkinter import ttk
from PIL import Image, ImageTk, ImageWin

import sys
import os
import os.path
import time
import tempfile
import io

import fitz

import six

import win32api
import win32print
import win32gui
import win32con
import win32ui

from MyFrame import MyFrame
from MultiEntryDialog import MultiEntryDialog
from PrinterDialog import PrinterDialog
from InsertPdfDialog import InsertPdfDialog
from ExportPdfDialog import ExportPdfDialog
from DelPdfDialog import DelPdfDialog

My_Global_CanVas_Pic = None  # canvas显示图片
My_Global_List_Imgs = []     # 缩略图列表

class PdfFrame(MyFrame):
    def __init__(self, master=None):
        super(PdfFrame,self).__init__(master)    # 调用父类初始化
        self.bOpened = False    # 文档是否打开
        self.pdfName = None     # 文档名
        self.bModified = False  # 是否修改
        self.nPages = 0      # 文档总页数
        self.nCurr = 0      # 当前文档页码
        self.docDoc = None      # 当前pymupdf文档对象
        self.nMaxPages = 99   # 最大显示页数
        self.labelList = []   # 缩略图列表
        self.lbpageList = []  # 缩略图页码列表
        self.m_pixmap = None  # 当前canvas中显示的图片
        self.pixmapw = 0      # 当前canvas中显示的图片宽度
        self.pixmaph = 0      # 当前canvas中显示的图片高度
        self.curRotation = 0   # 当前页面旋转角度

        # 处理命令行参数
        if len(sys.argv) > 1:
            self.pdfName = sys.argv[1]
            self.open_doc()

    # 打开文件
    def onclicked_actionOpen(self):
        self.pdfName = tk.filedialog.askopenfilename(defaultextension='.pdf',
                                                 filetypes=[('PDF files', '*.pdf')])
        if self.pdfName:
            self.open_doc()

    def open_doc(self):
        if self.bOpened:
            self.docDoc.close()
            self.bOpened = False  # 文档是否打开
            self.nPages = 0  # 文档总页数
            self.nCurr = -1  # 当前文档页码
            self.docDoc = None  # 当前pymupdf文档对象
            self.clear_cvcanvas() # 先清除页面canvas，再清除列表
            self.clear_list_canvas()

        if self.pdfName:
            self.docDoc = fitz.open(self.pdfName)
            self.bOpened = True  # 设置文件打开
            self.refresh_listWidget()    # 先打开列表，再显示第一页
            # 显示第一页
            self.nCurr = 0
            self.curRotation = 0
            self.show_current_page()
            filePath, filename = os.path.split(self.pdfName)
            self.UpdateRootTitle(filename)
        else:
            messagebox.showerror("错误", "未选择文件")


    # 刷新缩略图列表
    def refresh_listWidget(self):
        if not self.bOpened:
            return
        # 刷新菜单状态 pass
        # 清空缩略图列表
        self.clear_list_canvas()

        # self.nPages = self.docDoc.pageCount
        self.nPages = self.docDoc.page_count

        # 产生缩略图label
        if self.nPages <= 0:
            return

        global My_Global_List_Imgs
        for i in range(0, self.nPages):
            page = self.docDoc[i]  # 当前页
            zoom = int(100)    # mupdf输出页面缩放，100表示默认大小
            rotate = int(0)

            trans = fitz.Matrix(zoom / 100.0, zoom / 100.0).prerotate(rotate)

            pix = page.get_pixmap(matrix=trans, alpha=False)
            # set the mode depending on alpha
            mode = "RGBA" if pix.alpha else "RGB"
            # 计算缩略图缩小因子
            factX = 110 / pix.width
            factY = 155 / pix.height
            if factX > factY:
                w = int(pix.width * factY)
                h = int(pix.height * factY)
            else:
                w = int(pix.width * factX)
                h = int(pix.height *factX)
            img = Image.frombytes(mode, [pix.width, pix.height], pix.samples).resize((w,h))    # 生成当前页图片
            
            merge_img = Image.new('RGB', (110, 155), 0xffffff)     # 空白缩略图
            merge_img.paste(img, (0, 0))    # 将图片贴到空白缩略图中
            My_Global_List_Imgs.append(ImageTk.PhotoImage(merge_img))
            
            lb = tk.Label(self.labelframe, height=155, width=110 , image=My_Global_List_Imgs[i])    # 产生缩略图label
            lb.photo = My_Global_List_Imgs[i]    # 确保贴图成功
            lb.bind("<Double-Button-1>",self.handlerAdaptor(self.clickList,curr=i))   # 绑定鼠标双击，传递参数curr为当前页码
            lb.bind("<Button-3>", self.handlerAdaptor(self.set_lb_popup,curr=i))  # 绑定鼠标右击事件
            self.labelList.append(lb)


        canvasHeight = 0
        self.lbpageList = []    # 缩略图label列表，显示页码
        for j in range(0, self.nPages):
            l = self.labelList[j]        # 放置缩略图label
            l.pack()
            canvasHeight = canvasHeight + 155   # 缩略图高度
            lbpage = tk.Label(self.labelframe,text="第"+ str(j+1) + "页")    # 缩略图页码
            lbpage.pack()
            self.lbpageList.append(lbpage)
            canvasHeight = canvasHeight + 27    # 页码显示高度(默认行高)
        self.labelframe.update_idletasks()

        self.listCanvas.config(scrollregion=(0,0,100,canvasHeight))    # 列表滚动区域
        self.listCanvas.create_window((55, int(canvasHeight/2)), window=self.labelframe)  # create_window
        self.fm_list_scb_ver.pack(side=tk.TOP,fill=tk.Y,expand=True)    # 只允许上下扩展
        self.btnShow.config(image=self.imgleft)       # 缩略图栏目缩放按钮图标

        self.listCanvas.update()  # force screen redraw/update
        time.sleep(0.5)    # pause, but don't block gui


    # 清空缩略图列表
    def clear_list_canvas(self):
        #for lb in self.labelList:     # 删除列表中所有label
        #    lb.destroy()
        #for lbp in self.lbpageList:
        #    lbp.destroy()
        for widget in self.labelframe.winfo_children():
            widget.destroy()
        self.labelList.clear()
        self.lbpageList.clear()
        My_Global_List_Imgs.clear()    # 清除全局缩略图
        self.fm_list_scb_ver.pack_forget()    # 隐藏列表窗口

    # 清空cvcanvas
    def clear_cvcanvas(self):
        self.cvCanvas.delete(tk.ALL)    # 删除canvas中所有元素
        self.fm_canvas_scb_ver.pack_forget()    # 隐藏canvas窗口


    # 显示当前页
    def show_current_page(self):
        if not self.bOpened or self.nPages <= 0:
            return

        # 得到当前页
        self.m_factor = 1  # 当前缩放因子
        page = self.docDoc[self.nCurr]
        zoom = int(200)
        rotate = int(0)
        trans = fitz.Matrix(zoom / 100.0, zoom / 100.0).prerotate(rotate)
        pix = page.get_pixmap(matrix=trans, alpha=False)

        # set the mode depending on alpha
        mode = "RGBA" if pix.alpha else "RGB"
        self.m_pixmap = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
        self.pixmapw = pix.width
        self.pixmaph = pix.height
        global My_Global_CanVas_Pic    # 要用global变量，否则canvas不显示图片
        My_Global_CanVas_Pic = ImageTk.PhotoImage(self.m_pixmap.resize((self.pixmapw, self.pixmaph)))
        self.cvCanvas.create_image(int(self.pixmapw/2),int(self.pixmaph/2), image=My_Global_CanVas_Pic)
        self.cvCanvas.config(scrollregion=(0, 0, self.pixmapw, self.pixmaph))
        self.fm_canvas_scb_ver.pack(fill=tk.BOTH, expand=True)  # 放置canvas放置窗口
        self.cvCanvas.update()  # force screen redraw/update
        time.sleep(0.25)    # pause, but don't block gui
        self.UpdateStatus(f'{self.pdfName} - 第{self.nCurr + 1}/{self.nPages}页')

   # 双击缩略图显示当前页
    def clickList(self,event,curr):
        self.nCurr = curr
        self.curRotation = 0
        self.show_current_page()

    # 双击缩略图事件
    def handlerAdaptor(self,fun,**kwds):
        return lambda event,fun=fun,kwds=kwds:fun(event,**kwds)

    # 缩略图右击事件
    def set_lb_popup(self, event, curr):
        self.nCurr = curr
        self.curRotation = 0
        self.show_current_page()
        # 创建右键菜单
        right_click_menu = tk.Menu(self.labelframe, tearoff=False)
        right_click_menu.add_command(label="删除当前页面", command=lambda: self.on_menu_click_del(event))
        right_click_menu.add_separator()
        right_click_menu.add_command(label="向左旋转页面", command=lambda: self.on_menu_click_turnleft(event))
        right_click_menu.add_command(label="向右旋转页面", command=lambda: self.on_menu_click_turnright(event))
        right_click_menu.post(event.x_root, event.y_root)

    # 缩略图右击菜单-删除
    def on_menu_click_del(self,event):
        result = messagebox.askyesno("提醒", f"是否确认删除第{self.nCurr}页?")
        if not result:
            return
        self.docDoc.delete_page(self.nCurr)    # 删除指定页
        self.bModified = True
        self.refresh_listWidget()    # 刷新左侧列表
        if self.nCurr >= self.nPages:    # 显示当前页
            self.nCurr = self.nPages - 1
        self.show_current_page()


    # 缩略图右击菜单-左转
    def on_menu_click_turnleft(self,event):
        self.onclicked_actionRolateLeft()

    # 缩略图右击菜单-右转
    def on_menu_click_turnright(self,event):
        self.onclicked_actionRolateRight()

    # 查看canvas鼠标滚轮事件
    def canvasProcessWheel(self,event):
        if not self.bOpened or self.nPages <= 0:
            return
        if event.delta > 0:   # 滚轮往上滚动，放大            
            if self.m_factor < 2:
                self.m_factor = self.m_factor * 1.1
            else:
                return
        else:   # 滚轮往下滚动，缩小            
            if self.m_factor > 0.2:
                self.m_factor = self.m_factor * 0.9
            else:
                return
        global My_Global_CanVas_Pic

        w = int(self.pixmapw * self.m_factor)
        h = int(self.pixmaph * self.m_factor)

        self.cvCanvas.delete(tk.ALL)    # 删除canvas中所有元素
        My_Global_CanVas_Pic = ImageTk.PhotoImage(self.m_pixmap.resize((w,h)))
        self.cvCanvas.create_image(int(w/2), int(h/2), image=My_Global_CanVas_Pic)  # 放置图片
        self.cvCanvas.config(scrollregion=(0, 0, w, h))    # 设置滚动条
        self.cvCanvas.update()  # force screen redraw/update
        time.sleep(0.25)    # pause, but don't block gui



    # 关闭当前文件
    def onclicked_actionClose(self):
        if self.bOpened:
            self.docDoc.close()
        self.bOpened = False    # 文档是否打开
        self.pdfName = None     # 文档名
        self.nPages = 0         # 文档总页数
        self.nCurr = -1          # 当前文档页码
        self.docDoc = None      # 当前pymupdf文档对象
        self.curRotation = 0
        self.clear_cvcanvas()
        self.clear_list_canvas()

    # 保存文件
    def onclicked_actionSaveas(self):
        if not self.bOpened:
            return
        file_path = ""

        while not file_path:
            file_path = filedialog.asksaveasfilename(defaultextension='.pdf',
                                                 filetypes=[('PDF files', '*.pdf')])
            if file_path:
                if not file_path.endswith('.pdf'):
                    file_path += '.pdf'
                if file_path == self.pdfName:
                    messagebox.showerror("提示", "文件名不能与原始文件名相同，请重新选择")
                    file_path = ""
                    continue
            else:
                messagebox.showinfo("提示", "取消操作，没有保存")
                return

        self.docDoc.save(file_path)    # 保存
        # 先关闭当前文档
        self.bOpened = False  # 文档是否打开
        self.bModified = False
        self.pdfName = None  # 文档名
        self.nPages = 0  # 文档总页数
        self.nCurr = -1  # 当前文档页码
        self.docDoc = None  # 当前pymupdf文档对象
        # 再打开新文档
        self.pdfName = file_path
        self.docDoc = fitz.open(self.pdfName)
        self.bOpened = True  # 设置文件打开
        self.refresh_listWidget()
        # 显示第一页
        self.nCurr = 0
        self.curRotation = 0
        self.show_current_page()

        messagebox.showinfo("提示", "保存完成")

    # 打印文件
    def onclicked_actionPrint(self):
        if not self.bOpened:
            return
        self.nPages = self.docDoc.page_count
        if self.bModified:
            messagebox.showinfo("提示", "已修改，请先保存")
            return

        """安全地显示打印机选择对话框"""
        # Get default printer
        printer_name = win32print.GetDefaultPrinter()    # 先设置默认打印机
        pagestart = 1          # 默认打印页码，从1开始
        pageend = self.nPages

        # 创建并显示打印机选择对话框
        fields = ["打印机名", "开始页码", "终止页码"]
        defvalues = [printer_name, pagestart, pageend]

        root0 = tk.Tk()
        root0.withdraw()  # 隐藏主窗口
        dialog = tk.Toplevel(root0)
        dialog.title("设置打印参数")
        dialog.resizable(False, False)
        dlg = PrinterDialog(dialog, fields, defvalues)
        results = dlg.show()
        if results:    # 校验返回值
            if results[fields[0]]:
                printer_name = results[fields[0]]
            if results[fields[1]]:
                pagestart = int(results[fields[1]])
                if pagestart <= 0 or pagestart > self.nPages:
                    messagebox.showinfo("错误","起始页码错误")
                    return
            if results[fields[2]]:
                pageend = int(results[fields[2]])
                if pageend < pagestart:
                    messagebox.showinfo("错误", "终止页码错误")
                    return
                if pageend > self.nPages:
                    pageend = self.nPages
        else:    # 按了取消
            return

        result = messagebox.askyesno("Print", f"打印 {pagestart} - {pageend} 页到 {printer_name}?")
        if not result:
            return

        try:
            # Show printing dialog
            # Print each page
            printer_handle = win32print.OpenPrinter(printer_name)
            try:
                # Get printer information
                # printer_info = win32print.GetPrinter(printer_handle, 2)

                # Start document
                doc_info = ("Printing " + self.pdfName, None, "RAW")
                job_id = win32print.StartDocPrinter(printer_handle, 1, doc_info)

                hdc = win32ui.CreateDC()
                hdc.CreatePrinterDC(printer_name)
                hdc.StartDoc(self.pdfName)

                for page_num in range(pagestart-1,pageend):
                    page = self.docDoc.load_page(page_num)

                    # Render page at printer resolution (300 DPI)
                    pix = page.get_pixmap(dpi=300)
                    # Convert to PNG format (which is supported by PyMuPDF)
                    img_data = pix.tobytes("png")
                    # Create a PIL Image from the PNG data
                    img = Image.open(io.BytesIO(img_data))
                    # Start page
                    win32print.StartPagePrinter(printer_handle)
                    # Print the image using PIL's ImageWin module
                    hdc.StartPage()

                    # Calculate scaling to fit the page
                    printable_area = hdc.GetDeviceCaps(win32con.HORZRES), hdc.GetDeviceCaps(win32con.VERTRES)
                    printer_size = hdc.GetDeviceCaps(win32con.PHYSICALWIDTH), hdc.GetDeviceCaps(win32con.PHYSICALHEIGHT)
                    printer_margins = hdc.GetDeviceCaps(win32con.PHYSICALOFFSETX), hdc.GetDeviceCaps(
                        win32con.PHYSICALOFFSETY)

                    # Scale the image to fit the printable area
                    img_width = img.size[0]
                    img_height = img.size[1]

                    # Maintain aspect ratio
                    ratio = min(printable_area[0] / img_width, printable_area[1] / img_height)
                    new_width = int(img_width * ratio)
                    new_height = int(img_height * ratio)

                    # Draw the image
                    dib = ImageWin.Dib(img)
                    dib.draw(hdc.GetHandleOutput(), (
                        printer_margins[0],
                        printer_margins[1],
                        printer_margins[0] + new_width,
                        printer_margins[1] + new_height
                    ))

                    hdc.EndPage()
                    # End page
                    win32print.EndPagePrinter(printer_handle)

                hdc.EndDoc()
                hdc.DeleteDC()

                # End document
                win32print.EndDocPrinter(printer_handle)

            except Exception as e:
                # Cancel the print job if there was an error
                win32print.AbortPrinter(printer_handle)
                raise e
            finally:
                win32print.ClosePrinter(printer_handle)

            messagebox.showinfo("Success", "PDF 打印完成")

        except Exception as e:
            messagebox.showerror("错误", f"打印失败: {str(e)}")


    # 退出程序
    def onclicked_actionExit(self):
        self.on_close()               # 销毁窗口

    # 向当前文档插入新页
    def onclicked_actionInsert(self):
        if not self.bOpened:
            return

        # 选择插入的文件
        pdfFile = tk.filedialog.askopenfilename(defaultextension='.pdf',
                                                 filetypes=[('PDF files', '*.pdf')])
        if not pdfFile:
            return
        fields = ["插入的PDF", "位置", "页码"]
        defvalues = [pdfFile, 'after', 1, self.nPages]

        root0 = tk.Tk()
        root0.withdraw()  # 隐藏主窗口
        dialog = tk.Toplevel(root0)
        dialog.title("插入PDF方式")
        dialog.resizable(False, False)

        dlg = InsertPdfDialog(dialog, fields, defvalues)

        results = dlg.show()

        if not results:
            return

        npage = 0
        # print("用户输入的内容:")
        # for field, value in results.items():
        #     print(f"{field}: {value}")
        if results[fields[1]] and results[fields[2]]:
            n = int(results[fields[2]])
            if n <= 0 or n > self.nPages:
                messagebox.showinfo("错误", "页码设置错误")
                return
            if results[fields[1]] == 'after':
                npage = n
            else:
                npage = n - 1
        else:
                return
        # print(npage)
        doc2 = fitz.open(pdfFile)
        self.docDoc.insert_pdf(doc2, from_page = 0, to_page = doc2.page_count - 1, start_at = npage)
        self.bModified = True
        self.refresh_listWidget()

    # 导出指定页到文件
    def onclicked_actionExport(self):
        if not self.bOpened:
            return
        fields = ["from", "to", "页码"]
        defvalues = [1, self.nPages, self.nPages]

        root0 = tk.Tk()
        root0.withdraw()  # 隐藏主窗口
        dialog = tk.Toplevel(root0)
        dialog.title("导出PDF页码范围")
        dialog.resizable(False, False)

        dlg = ExportPdfDialog(dialog, fields, defvalues)
        results = dlg.show()

        if not results:
            return

        if results[fields[0]] and results[fields[1]]:
            nfrom = int(results[fields[0]]) - 1
            nto = int(results[fields[1]]) - 1
            if nfrom < 0 or nfrom >= self.nPages or nto < 0 or nto >= self.nPages or nfrom > nto:
                messagebox.showinfo("错误", "页码设置错误")
                return
        else:
                return

        file_path = ""

        while not file_path:
            file_path = filedialog.asksaveasfilename(defaultextension='.pdf',
                                                 filetypes=[('PDF files', '*.pdf')])
            if file_path:
                if not file_path.endswith('.pdf'):
                    file_path += '.pdf'
                if file_path == self.pdfName:
                    messagebox.showerror("提示", "文件名不能与原始文件名相同，请重新选择")
                    file_path = ""
                    continue
            else:
                messagebox.showinfo("提示", "取消操作，没有保存")
                return

            if file_path:
                doc2 = fitz.open()                 # new empty PDF
                doc2.insert_pdf(self.docDoc, from_page=nfrom, to_page=nto)
                doc2.save(file_path)

    # 删除指定页
    def onclicked_actionDelete(self):
        if not self.bOpened:
            return
        fields = ["from", "to", "页码"]
        defvalues = [1, self.nPages, self.nPages]

        root0 = tk.Tk()
        root0.withdraw()  # 隐藏主窗口
        dialog = tk.Toplevel(root0)
        dialog.title("删除PDF页码范围")
        dialog.resizable(False, False)

        dlg = DelPdfDialog(dialog, fields, defvalues)
        results = dlg.show()

        if not results:
            return

        if results[fields[0]] and results[fields[1]]:
            nfrom = int(results[fields[0]]) - 1
            nto = int(results[fields[1]]) - 1
            if nfrom < 0 or nfrom >= self.nPages or nto < 0 or nto >= self.nPages or nfrom > nto:
                messagebox.showinfo("错误", "页码设置错误")
                return
            if nto - nfrom + 1 == self.nPages:
                messagebox.showinfo("错误", "至少得留下1页吧")
                return
        else:
                return

        self.docDoc.delete_pages(from_page=nfrom, to_page=nto)    # 删除指定页
        self.bModified = True
        self.refresh_listWidget()    # 刷新左侧列表
        if self.nCurr >= self.nPages:    # 显示当前页
            self.nCurr = self.nPages - 1
        self.show_current_page()

    # 旋转当前页
    def onclicked_actionRolateLeft(self):
        if not self.bOpened:
            return
        # 旋转页面，参数为旋转角度（顺时针），例如旋转90度
        # 可以是90, 180, 270等
        self.curRotation = (self.curRotation + 270) % 360
        page = self.docDoc[self.nCurr]
        rotated_page = page.set_rotation(rotation=self.curRotation)
        self.bModified = True

        self.refresh_listWidget()    # 刷新左侧列表
        self.show_current_page()

    def onclicked_actionRolateRight(self):
        if not self.bOpened:
            return
        # 旋转页面，参数为旋转角度（顺时针），例如旋转90度
        # 可以是90, 180, 270等
        self.curRotation = (self.curRotation + 90) % 360
        page = self.docDoc[self.nCurr]
        rotated_page = page.set_rotation(rotation=self.curRotation)
        self.bModified = True

        self.refresh_listWidget()    # 刷新左侧列表
        self.show_current_page()


    def onclicked_actionZoomIn(self):
        if not self.bOpened:
            return  
        self.m_factor = self.m_factor * 1.1

        global My_Global_CanVas_Pic

        w = int(self.pixmapw * self.m_factor)
        h = int(self.pixmaph * self.m_factor)

        self.cvCanvas.delete(tk.ALL)    # 删除canvas中所有元素
        My_Global_CanVas_Pic = ImageTk.PhotoImage(self.m_pixmap.resize((w,h)))
        self.cvCanvas.create_image(int(w/2), int(h/2), image=My_Global_CanVas_Pic)  # 放置图片
        self.cvCanvas.config(scrollregion=(0, 0, w, h))    # 设置滚动条
        self.cvCanvas.update()  # force screen redraw/update
        time.sleep(0.25)    # pause, but don't block gui

    def onclicked_actionZoomOut(self):
        if not self.bOpened:
            return
        self.m_factor = self.m_factor * 0.9

        global My_Global_CanVas_Pic

        w = int(self.pixmapw * self.m_factor)
        h = int(self.pixmaph * self.m_factor)

        self.cvCanvas.delete(tk.ALL)    # 删除canvas中所有元素
        My_Global_CanVas_Pic = ImageTk.PhotoImage(self.m_pixmap.resize((w,h)))
        self.cvCanvas.create_image(int(w/2), int(h/2), image=My_Global_CanVas_Pic)  # 放置图片
        self.cvCanvas.config(scrollregion=(0, 0, w, h))    # 设置滚动条
        self.cvCanvas.update()  # force screen redraw/update
        time.sleep(0.25)    # pause, but don't block gui


    def onclicked_actionHelp(self):
        messagebox.showinfo("帮助", "暂无帮助文档")

    def onclicked_actionAbout(self):
        messagebox.showinfo("关于", "Simple PDF Tool V1.0")


if __name__ == "__main__":
    root = tk.Tk()
    root.title('Simple PDF Tool')
    sw = root.winfo_screenwidth()
    # 得到屏幕宽度
    sh = root.winfo_screenheight()
    # 得到屏幕高度
    ww = 800
    wh= 600
    # 窗口宽高
    x = (sw-ww) / 2
    y = (sh-wh) / 2
    # 窗口偏移
    root.geometry("%dx%d+%d+%d" %(ww, wh, x, y))
    PdfFrame(root)
    root.mainloop()
